package maze.model;

import java.util.ArrayList;
import java.util.List;

import maze.model.type.Face;
import maze.model.type.Wall;
import maze.util.Util;

public class Map
{
	public Pair size;
	public Cell[][] cells;

	private static Map instance = null;

	/**
	 * @return the maze map instance
	 */
	public static Map getInstance()
	{
		if (instance == null)
			instance = new Map();
		return instance;
	}

	private Map()
	{
	}

	/**
	 * randomly generate the maze map
	 */
	public void generate()
	{
		cells = new Cell[size.x][size.y];
		fillWithBlockedWalls();
		markBorderWalls();
		knockDownWalls();
	}

	/**
	 * create all the cells with the blocked walls
	 */
	private void fillWithBlockedWalls()
	{
		for (int i = 0; i < size.x; i++)
		{
			for (int j = 0; j < size.y; j++)
			{
				// make cell
				cells[i][j] = new Cell();
				Pair position = new Pair(i, j);
				cells[i][j].position = position;

				// make walls
				for (Face face : Face.values())
				{
					cells[i][j].walls.put(face, Wall.BLOCKED);
				}
			}
		}
	}

	/**
	 * mark all the border walls with border status
	 */
	private void markBorderWalls()
	{
		for (int i = 0; i < size.x; i++)
		{
			// bottom border
			cells[i][0].walls.put(Face.SOUTH, Wall.BORDER);

			// top border
			cells[i][size.y - 1].walls.put(Face.NORTH, Wall.BORDER);
		}

		for (int j = 0; j < size.y; j++)
		{
			// left border
			cells[0][j].walls.put(Face.WEST, Wall.BORDER);

			// right border
			cells[size.x - 1][j].walls.put(Face.EAST, Wall.BORDER);
		}
	}

	/**
	 * knock down the walls to generate a maze
	 */
	private void knockDownWalls()
	{
		// initially, each cell list contains one cell
		List<List<Cell>> allCellList = new ArrayList<List<Cell>>();
		for (int i = 0; i < size.x; i++)
		{
			for (int j = 0; j < size.y; j++)
			{
				List<Cell> singleCellList = new ArrayList<Cell>();
				singleCellList.add(cells[i][j]);
				allCellList.add(singleCellList);
			}
		}

		// loop until only one cell list in allCellList
		while (allCellList.size() > 1)
		{
			// randomly look for a cell
			Pair pos = randomPosition();
			Cell cell = cells[pos.x][pos.y];

			// randomly look for a face of the cell
			List<Face> blockedFaces = cell.blockedFaces();
			if (blockedFaces.size() > 0)
			{
				int faceIndex = Util.random(blockedFaces.size());
				Face face = blockedFaces.get(faceIndex);

				// get the adjacent cell according to the face
				Pair adjPos = cell.adjacentCellPosition(face);
				if (adjPos != null && adjPos.x >= 0 && adjPos.x < size.x && adjPos.y >= 0 && adjPos.y < size.y)
				{
					Cell adjCell = cells[adjPos.x][adjPos.y];

					// make sure that the two cells are in the different cell lists
					List<Cell> cellList = cellListIn(allCellList, cell);
					List<Cell> adjCellList = cellListIn(allCellList, adjCell);
					if (cellList != adjCellList)
					{
						// knock down the wall between the two cells
						cell.walls.put(face, Wall.PASSED);
						adjCell.walls.put(face.opposite(), Wall.PASSED);

						// merge the two different cell lists
						allCellList.remove(cellList);
						allCellList.remove(adjCellList);
						cellList.addAll(adjCellList);
						allCellList.add(cellList);
					}
				}
			}
		}
	}

	/**
	 * get the cell list that contains the cell
	 */
	private List<Cell> cellListIn(List<List<Cell>> allCellList, Cell cell)
	{
		for (List<Cell> cellList : allCellList)
		{
			if (cellList.contains(cell))
			{
				return cellList;
			}
		}
		return null;
	}

	public Pair randomPosition()
	{
		return new Pair(Util.random(size.x), Util.random(size.y));
	}

	/**
	 * display the maze map
	 */
	public void display()
	{
		System.out.println("X=" + size.x + ", Y=" + size.y);

		for (int j = size.y - 1; j >= 0; j--)
		{
			for (int i = 0; i < size.x; i++)
			{
				if (!cells[i][j].canWallPass(Face.NORTH))
					System.out.print(" _ ");
				else
					System.out.print("   ");
			}
			System.out.println();
			for (int i = 0; i < size.x; i++)
			{
				if (!cells[i][j].canWallPass(Face.WEST))
					System.out.print("|");
				else
					System.out.print(" ");
				System.out.print("*");
				if (!cells[i][j].canWallPass(Face.EAST))
					System.out.print("|");
				else
					System.out.print(" ");
			}
			System.out.println();
			for (int i = 0; i < size.x; i++)
			{
				if (!cells[i][j].canWallPass(Face.SOUTH))
					System.out.print(" - ");
				else
					System.out.print("   ");
			}
			System.out.println();
		}
	}
}
